/* * $Id$ * * Copyright (c) 2000-2003 by Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.build.widget; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Point; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragSource; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import VASSAL.build.Buildable; import VASSAL.build.Builder; import VASSAL.build.Configurable; import VASSAL.build.GameModule; import VASSAL.build.GpIdSupport; import VASSAL.build.Widget; import VASSAL.build.module.Map; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.documentation.HelpWindow; import VASSAL.build.module.documentation.HelpWindowExtension; import VASSAL.build.module.map.MenuDisplayer; import VASSAL.build.module.map.PieceMover.AbstractDragHandler; import VASSAL.command.AddPiece; import VASSAL.command.Command; import VASSAL.configure.Configurer; import VASSAL.counters.BasicPiece; import VASSAL.counters.Decorator; import VASSAL.counters.DragBuffer; import VASSAL.counters.GamePiece; import VASSAL.counters.KeyBuffer; import VASSAL.counters.PieceCloner; import VASSAL.counters.PieceDefiner; import VASSAL.counters.PlaceMarker; import VASSAL.counters.Properties; import VASSAL.i18n.ComponentI18nData; /** * A Component that displays a GamePiece. * * Can be added to any Widget but cannot contain any children Keyboard input on * a PieceSlot is forwarded to the {@link GamePiece#keyEvent} method for the * PieceSlot's GamePiece. Clicking on a PieceSlot initiates a drag */ public class PieceSlot extends Widget implements MouseListener, KeyListener { public static final String GP_ID = "gpid"; protected GamePiece c; protected GamePiece expanded; protected String name; protected String pieceDefinition; protected static Font FONT = new Font("Dialog", 0, 12); protected JPanel panel; protected int width, height; protected String gpId = ""; // Unique PieceSlot Id protected GpIdSupport gpidSupport; public PieceSlot() { panel = new PieceSlot.Panel(this); panel.addMouseListener(this); panel.addKeyListener(this); } public PieceSlot(PieceSlot piece) { this(); copyFrom(piece); } public PieceSlot(CardSlot card) { this((PieceSlot) card); } protected void copyFrom(PieceSlot piece) { c = piece.c; name = piece.name; pieceDefinition = piece.pieceDefinition; gpidSupport = piece.gpidSupport; gpId = piece.gpId; } public class Panel extends JPanel { private static final long serialVersionUID = 1L; protected PieceSlot pieceSlot; public Panel(PieceSlot slot) { super(); setFocusTraversalKeysEnabled(false); pieceSlot = slot; } public PieceSlot getPieceSlot() { return pieceSlot; } public void paint(Graphics g) { PieceSlot.this.paint(g); } public Dimension getPreferredSize() { return PieceSlot.this.getPreferredSize(); } } public PieceSlot(GamePiece p) { this(); setPiece(p); } public void setPiece(GamePiece p) { c = p; clearExpandedPiece(); if (c != null) { final Dimension size = panel.getSize(); c.setPosition(new Point(size.width / 2, size.height / 2)); name = Decorator.getInnermost(c).getName(); } panel.revalidate(); panel.repaint(); pieceDefinition = c == null ? null : GameModule.getGameModule().encode(new AddPiece(c)); } /** * Return defined GamePiece with prototypes fully expanded. * * @return expanded piece */ protected GamePiece getExpandedPiece() { if (expanded == null) { final GamePiece p = getPiece(); if (p != null) { // Possible when PlaceMarker is building expanded = PieceCloner.getInstance().clonePiece(p); } } return expanded; } protected void clearExpandedPiece() { expanded = null; } /** * Return defined GamePiece with prototypes unexpanded. * * @return unexpanded piece */ public GamePiece getPiece() { if (c == null && pieceDefinition != null) { final AddPiece comm = (AddPiece) GameModule.getGameModule().decode(pieceDefinition); if (comm == null) { System.err.println("Couldn't build piece " + pieceDefinition); pieceDefinition = null; } else { c = comm.getTarget(); c.setState(comm.getState()); final Dimension size = panel.getSize(); c.setPosition(new Point(size.width / 2, size.height / 2)); } } if (c != null) { c.setProperty(Properties.PIECE_ID, getGpId()); } return c; } public void paint(Graphics g) { final Dimension size = panel.getSize(); final Color c = g.getColor(); g.setColor(Color.WHITE); g.fillRect(0, 0, size.width, size.height); g.setColor(c); if (getExpandedPiece() == null) { final FontMetrics fm = g.getFontMetrics(); g.drawRect(0, 0, size.width - 1, size.height - 1); g.setFont(FONT); g.drawString(" nil ", size.width/2 - fm.stringWidth(" nil ")/2, size.height/2 ); } else { getExpandedPiece().draw(g, size.width / 2, size.height / 2, panel, 1.0); // NB: The piece, not the expanded piece, receives events, so we check // the piece, not the expanded piece, for its selection status. if (Boolean.TRUE.equals(getPiece().getProperty(Properties.SELECTED))) { BasicPiece.getHighlighter().draw(getExpandedPiece(), g, size.width / 2, size.height / 2, panel, 1.0); } } } public Dimension getPreferredSize() { if (c != null && panel.getGraphics() != null) { // c.draw(panel.getGraphics(), 0, 0, panel, 1.0); return c.boundingBox().getSize(); } else { return new Dimension(width, height); } } public void mousePressed(MouseEvent e) { KeyBuffer.getBuffer().clear(); Map.clearActiveMap(); if (getPiece() != null) { KeyBuffer.getBuffer().add(getPiece()); } clearExpandedPiece(); panel.requestFocus(); panel.repaint(); } // Puts counter in DragBuffer. Call when mouse gesture recognized protected void startDrag() { // Recenter piece; panel may have been resized at some point resulting // in pieces with inaccurate positional information. final Dimension size = panel.getSize(); getPiece().setPosition(new Point(size.width / 2, size.height / 2)); // Erase selection border to avoid leaving selected after mouse dragged out getPiece().setProperty(Properties.SELECTED, null); panel.repaint(); if (getPiece() != null) { KeyBuffer.getBuffer().clear(); DragBuffer.getBuffer().clear(); GamePiece newPiece = PieceCloner.getInstance().clonePiece(getPiece()); newPiece.setProperty(Properties.PIECE_ID, getGpId()); DragBuffer.getBuffer().add(newPiece); } } public void mouseReleased(MouseEvent e) { if (getPiece() != null && e.isMetaDown()) { JPopupMenu popup = MenuDisplayer.createPopup(getPiece()); popup.addPopupMenuListener(new PopupMenuListener() { public void popupMenuCanceled(PopupMenuEvent evt) { panel.repaint(); } public void popupMenuWillBecomeInvisible(PopupMenuEvent evt) { clearExpandedPiece(); panel.repaint(); } public void popupMenuWillBecomeVisible(PopupMenuEvent evt) { } }); popup.show(panel, e.getX(), e.getY()); } clearExpandedPiece(); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { KeyBuffer.getBuffer().remove(getPiece()); clearExpandedPiece(); panel.repaint(); } public void keyPressed(KeyEvent e) { KeyBuffer.getBuffer().keyCommand(KeyStroke.getKeyStrokeForEvent(e)); e.consume(); clearExpandedPiece(); panel.repaint(); } public void keyTyped(KeyEvent e) { KeyBuffer.getBuffer().keyCommand(KeyStroke.getKeyStrokeForEvent(e)); e.consume(); clearExpandedPiece(); panel.repaint(); } public void keyReleased(KeyEvent e) { KeyBuffer.getBuffer().keyCommand(KeyStroke.getKeyStrokeForEvent(e)); e.consume(); clearExpandedPiece(); panel.repaint(); } public static String getConfigureTypeName() { return "Single piece"; } public Component getComponent() { return panel; } /** * When building a PieceSlot, the text contents of the XML element are parsed * into a String. The String is decoded using {@link GameModule#decode}. The * resulting {@link Command} should be an instance of {@link AddPiece}. The * piece referred to in the Command becomes the piece contained in the * PieceSlot */ public void build(org.w3c.dom.Element e) { gpidSupport = GameModule.getGameModule().getGpIdSupport(); if (e != null) { name = e.getAttribute(NAME); gpId = e.getAttribute(GP_ID) + ""; if (name.length() == 0) { name = null; } try { width = Integer.parseInt(e.getAttribute(WIDTH)); height = Integer.parseInt(e.getAttribute(HEIGHT)); } catch (NumberFormatException ex) { // Use default values. Will be overwritten when module is saved width = 60; height = 60; } pieceDefinition = Builder.getText(e); c = null; } } public void addTo(Buildable parent) { panel.setDropTarget(AbstractDragHandler.makeDropTarget(panel, DnDConstants.ACTION_MOVE, null)); DragGestureListener dragGestureListener = new DragGestureListener() { public void dragGestureRecognized(DragGestureEvent dge) { startDrag(); AbstractDragHandler.getTheDragHandler().dragGestureRecognized(dge); } }; DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(panel, DnDConstants.ACTION_MOVE, dragGestureListener); } public org.w3c.dom.Element getBuildElement(org.w3c.dom.Document doc) { final org.w3c.dom.Element el = doc.createElement(getClass().getName()); final String s = getConfigureName(); if (s != null) { el.setAttribute(NAME, s); } el.setAttribute(GP_ID, gpId+""); el.setAttribute(WIDTH, getPreferredSize().width + ""); el.setAttribute(HEIGHT, getPreferredSize().height + ""); if (c != null || pieceDefinition != null) { el.appendChild(doc.createTextNode( c == null ? pieceDefinition : GameModule.getGameModule().encode(new AddPiece(c)) )); } return el; } public void removeFrom(Buildable parent) { } public String getConfigureName() { if (name != null) { return name; } else if (getPiece() != null) { return Decorator.getInnermost(getPiece()).getName(); } else { return null; } } public HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("GamePiece.htm"); } public String[] getAttributeNames() { return new String[0]; } public String[] getAttributeDescriptions() { return new String[0]; } public Class<?>[] getAttributeTypes() { return new Class<?>[0]; } public void setAttribute(String name, Object value) { } /** * @return an array of Configurer objects representing the Buildable children * of this Configurable object */ public Configurable[] getConfigureComponents() { return new Configurable[0]; } /** * @return an array of Configurer objects representing all possible classes of * Buildable children of this Configurable object */ public Class<?>[] getAllowableConfigureComponents() { return new Class[0]; } /* * Redirect getAttributeValueString() to return the attribute * values for the enclosed pieces */ public String getAttributeValueString(String attr) { return getI18nData().getLocalUntranslatedValue(attr); } public ComponentI18nData getI18nData() { /* * Piece can change due to editing, so cannot cache the I18nData */ return new ComponentI18nData(this, getPiece()); } public Configurer getConfigurer() { return new MyConfigurer(this); } /** * Update the gpid for this PieceSlot, using the given {@link GpIdSupport} * to generate the new id. */ public void updateGpId(GpIdSupport s) { gpidSupport = s; updateGpId(); } /** * Allocate a new gpid to this PieceSlot, plus to any PlaceMarker or * Replace traits. */ public void updateGpId() { gpId = gpidSupport.generateGpId(); GamePiece piece = getPiece(); updateGpId(piece); setPiece(piece); } /** * Allocate new gpid's in the given GamePiece * * @param piece GamePiece */ public void updateGpId(GamePiece piece) { if (piece == null || piece instanceof BasicPiece) { return; } if (piece instanceof PlaceMarker) { ((PlaceMarker) piece).setGpId(gpidSupport.generateGpId()); } updateGpId(((Decorator) piece).getInner()); } public String getGpId() { return gpId; } public void setGpId(String id) { gpId = id; } private static class MyConfigurer extends Configurer implements HelpWindowExtension { private PieceDefiner definer; public MyConfigurer(PieceSlot slot) { super(null, slot.getConfigureName(), slot); definer = new PieceDefiner(slot.getGpId(), slot.gpidSupport); definer.setPiece(slot.getPiece()); } @Deprecated public void setBaseWindow(HelpWindow w) { } public String getValueString() { return null; } public void setValue(String s) { throw new UnsupportedOperationException("Cannot set from String"); } public Object getValue() { PieceSlot slot = (PieceSlot) super.getValue(); if (slot != null) { slot.setPiece(definer.getPiece()); } return slot; } public Component getControls() { return definer; } } }